• 问题

    compareTo方法是Comparable接口中唯一的方法,不但允许进行简单的等同性比较,而且允许执行顺序比较。一旦实现了Comparable接口,就可以跟许多泛型方法以及依赖于该接口的集合实现类进行协作。实现CompareTo方法有哪些规范?

  • 解决

    1. 使用compareTo方法有一个重要的约定,就是通常情况下compareTo方法施加的等同性测试和equals方法一致。如果不一致的话,集合接口一般是使用equals方法来进行等同性测试,而有序集合是采用compareTo方法进行等同性测试,如果两者不一致的话,容易造成灾难性的后果;

    2. 将对象与指定的对象进行比较。当该对象小于、等于或者大于指定对象的时候,分别返回一个负整数,零或者正整数,如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException。在下面的说明中,符号sgn(表达式)表示数学中的signum函数,它根据表达式(expression)的值为负值、零和正值,分别返回-1、0、1。

      • 必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。这也暗示着当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才抛出异常。
      • 必须确保这个比较关系是可传递的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示着x.compareTo(z) > 0也成立。对应着equals使用规范里面的传递性
      • 必须确保x.compareTo(y) == 0暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))。
      • 强烈建议(x.compareTo(y) == 0) == (x.equals(y)),但是这个并非绝对必要。一般来说,任何实现了Comparable接口的类,若违反了这个条件,都应该明确予以说明。推荐使用这样的说法:“注意,该类具有内在的排序功能,但是与equals不一致”。
    3. 示例

      如果一个类有多个关键域,那么比较这些关键域的顺序非常关键。必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果(0代表着相等),则整个比较操作结束,并返回该结果。如果最关键的域是相等的,则再比较下一个关键域,以此类推,如果所有域都是相等的,那么才返回0。例如下面的例子:

      public final class PhoneNumber implements Comparable {
      
          private final short areaCode;
          private final short prefix;
          private final short lineNumber;
      
          public PhoneNumber(int areaCode, int prefix,
                              int lineNumber) {
              this.areaCode = (short) areaCode;
              this.prefix = (short) prefix;
              this.lineNumber = (short) lineNumber;
          }
      
          @Override
          public int compareTo(PhoneNumber pn) {
              if (areaCode < pn.areaCode) 
                  return -1;
              if(areaCode > pn.areaCode)
                  return 1;
      
              if (prefix < pn.prefix)
                  return -1;
              if (prefix > pn.prefix)
                  return 1;
      
              if (lineNumber < pn.lineNumber)
                  return -1;
              if (lineNumber > pn.lineNumber)
                  return 1;
      
              return 0;
          }
      }
      

      可以改进如下:

      public int compareTo(PhoneNumber pn) {
          int areaCodeDiff = areaCode - pn.areaCode;
          if (areaCodeDiff != 0)
              return areaCodeDiff;
      
          int prefixDiff = prefix - pn.prefix;
          if (0 != prefixDiff)
              return prefixDiff;
      
          return lineNumber - pn.lineNumber;
      }
      

      使用这种方法的时候需要注意,有符号的32位整数还不足以大到能够表达任意两个32位整数的差值,如果i是一个很大的正整数,j是一个很小的负整数,i-j有可能会溢出,并且返回一个负值。

  • 结论

    在实现Comparable接口时,应该遵守这些规范,特别是在做等同性测试的时候,要和equals等同性测试结果保持一致。

results matching ""

    No results matching ""